﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Text;
using DDotNet.ORM.DataFactory.Exception;
using DDotNet.ORM.DataFactory.Helper;
using DDotNet.ORM.DataFactory.Interface;
using DDotNet.ORM.ORMapping.Entity;
using DDotNet.ORM.ORMapping.OREntity;

namespace DDotNet.ORM.DataFactory.DirectAccess.InternalFactory
{
    /// <summary>
    /// Internal Direct Access Entities Only CRUD Factory
    /// </summary>
    internal sealed class DirectAccessCRUDFactory : IInternalCRUDFactory
    {
        #region Constructor

        private DirectAccessCRUDFactory()
        {

        }
        
        public DirectAccessCRUDFactory(ICrudFactory parentICCrudFactory)
        {
            this._Parent = parentICCrudFactory;
        }
        
        #endregion
        
        #region IInternalCRUDFactory

        /// <summary>
        /// Insere uma nova Cópia do Objeto no Banco de Dados
        /// </summary>
        /// <param name="mappedEntity">Entidade a ser Salva no Banco de Dados</param>
        public void InsertEntityIntoDB(BusinessEntityBase mappedEntity, CRUDHelper crudHelper)
        {
            // Create Objects
            IDbCommand dbCommand =
                crudHelper.GetDataBaseFactory(MappedEntityHelper.Instance.GetConnectionStringKey(mappedEntity)).GetNewCommand();
            List<IDataParameter> parameters;

            // Check If Entity Supports a Insert
            MappedEntityHelper.Instance.CheckEntitySupportsInsert(mappedEntity);

            // Get ODBC TextCommand
            dbCommand.CommandText = this.GetInsertCommandText(mappedEntity, crudHelper);

            // Setup Generic ODBC Command Settings
            dbCommand.CommandType = CommandType.Text;

            // Get Parameters
            parameters =
                MappedEntityHelper.Instance.GetInsertDataParameters(mappedEntity,
                                                                    crudHelper.GetDataBaseFactory(
                                                                        MappedEntityHelper.Instance.
                                                                            GetConnectionStringKey(mappedEntity)), this._Parent);

            // Add Parameters
            if (parameters != null)
            {
                foreach (IDbDataParameter parameter in parameters)
                {
                    dbCommand.Parameters.Add(parameter);
                }
            }

            // Check for Compiled Version
            if (!this._InsertScriptCompiledVersion.ContainsKey(mappedEntity.GetType()))
            {
                // Create a Compiled Version of Insert Script
                this.CreateCompiledCommandVersion(dbCommand);

                // Update HashTable Info
                this._InsertScriptCompiledVersion.Add(mappedEntity.GetType(), true);
            }

            // Execute Query
            dbCommand.ExecuteNonQuery();

            // Check If Have a Auto Generated Primary Key and Get the New Value
            this.GetNewPK(mappedEntity, crudHelper);

            // Mark Old and Clean
            mappedEntity.MarkOld();
            mappedEntity.CleanDirty();
        }

        /// <summary>
        /// Constains a Indication for First "SELECT @@Identity" Script Execution to Create a Compiled Version Into DataBase Command
        /// </summary>
        private bool _IsFristGetNewPKScript = false;
        
        /// <summary>
        /// Atualiza um Objeto no Banco de Dados
        /// </summary>
        /// <param name="mappedEntity">Entidade a ser Atualizada no Banco de Dados</param>
        public void UpdateEntityIntoDB(BusinessEntityBase mappedEntity, CRUDHelper crudHelper)
        {
            // Create Objects
            IDbCommand dbCommand =
                crudHelper.GetDataBaseFactory(MappedEntityHelper.Instance.GetConnectionStringKey(mappedEntity)).GetNewCommand();
            List<IDataParameter> parameters;
            List<IDataParameter> pkParameters;

            // Check If Entity Supports a Update
            MappedEntityHelper.Instance.CheckEntitySupportsUpdate(mappedEntity);

            // Get ODBC TextCommand
            dbCommand.CommandText = this.GetUpdateCommandText(mappedEntity, crudHelper);

            // Setup Generic ODBC Command Settings
            dbCommand.CommandType = CommandType.Text;

            // Get Parameters
            parameters =
                MappedEntityHelper.Instance.GetUpdateDataParameters(mappedEntity,
                                                                    crudHelper.GetDataBaseFactory(
                                                                        MappedEntityHelper.Instance.
                                                                            GetConnectionStringKey(mappedEntity)), this._Parent);
            pkParameters =
                MappedEntityHelper.Instance.GetPrimaryKeyWhereDataParameters(mappedEntity,
                                                                             crudHelper.GetDataBaseFactory(
                                                                                 MappedEntityHelper.Instance.
                                                                                     GetConnectionStringKey(mappedEntity)),
                                                                             this._Parent);

            // Add Parameters
            if (parameters != null)
            {
                foreach (IDbDataParameter parameter in parameters)
                {
                    dbCommand.Parameters.Add(parameter);
                }
            }

            if (pkParameters != null)
            {
                foreach (IDbDataParameter pkParameter in pkParameters)
                {
                    dbCommand.Parameters.Add(pkParameter);
                }
            }

            // Check for Compiled Version
            if (!this._UpdateScriptCompiledVersion.ContainsKey(mappedEntity.GetType()))
            {
                // Create a Compiled Version of Insert Script
                this.CreateCompiledCommandVersion(dbCommand);

                // Update HashTable Info
                this._UpdateScriptCompiledVersion.Add(mappedEntity.GetType(), true);
            }

            // Execute Query
            dbCommand.ExecuteNonQuery();

            // Mark Old and Clean
            mappedEntity.MarkOld();
            mappedEntity.CleanDirty();
        }

        /// <summary>
        /// Exclui um Objeto no Banco de Dados
        /// </summary>
        /// <param name="mappedEntity">Entidade a ser Excluída do Banco de Dados</param>
        public void DeleteEntityIntoDB(BusinessEntityBase mappedEntity, CRUDHelper crudHelper)
        {
            // Create Objects
            IDbCommand dbCommand =
                crudHelper.GetDataBaseFactory(MappedEntityHelper.Instance.GetConnectionStringKey(mappedEntity)).GetNewCommand();
            List<IDataParameter> parameters;

            // Check If Entity Supports a Delete
            MappedEntityHelper.Instance.CheckEntitySupportsDelete(mappedEntity);

            // Get ODBC TextCommand
            dbCommand.CommandText = this.GetDeleteCommandText(mappedEntity, crudHelper);

            // Setup Generic ODBC Command Settings
            dbCommand.CommandType = CommandType.Text;

            // Get Parameters
            parameters =
                MappedEntityHelper.Instance.GetPrimaryKeyWhereDataParameters(mappedEntity,
                                                                             crudHelper.GetDataBaseFactory(
                                                                                 MappedEntityHelper.Instance.
                                                                                     GetConnectionStringKey(mappedEntity)),
                                                                             this._Parent);

            // Add Parameters
            if (parameters != null)
            {
                foreach (IDbDataParameter parameter in parameters)
                {
                    dbCommand.Parameters.Add(parameter);
                }
            }

            // Check for Compiled Version
            if (!this._DeleteScriptCompiledVersion.ContainsKey(mappedEntity.GetType()))
            {
                // Create a Compiled Version of Insert Script
                this.CreateCompiledCommandVersion(dbCommand);

                // Update HashTable Info
                this._DeleteScriptCompiledVersion.Add(mappedEntity.GetType(), true);
            }

            // Execute Query
            dbCommand.ExecuteNonQuery();
        }

        /// <summary>
        /// Limpa uma Tabela do Banco de Dados
        /// </summary>
        /// <param name="mappedEntityType">Tipo da Entidade Correspondente a Tabela a ser Limpa</param>
        public void DeleteAll(Type mappedEntityType, CRUDHelper crudHelper)
        {
            // Create Objects
            IDbCommand dbCommand =
                crudHelper.GetDataBaseFactory(MappedEntityHelper.Instance.GetConnectionStringKey(mappedEntityType)).GetNewCommand();

            // Get ODBC TextCommand
            dbCommand.CommandText = this.GetDeleteAllCommandText(mappedEntityType);

            // Setup Generic ODBC Command Settings
            dbCommand.CommandType = CommandType.Text;

            try
            {
                // Execute Query
                dbCommand.ExecuteNonQuery();
            }
            catch (System.Exception ex)
            {
                // Rollback Transaction
                crudHelper.RollbackTransaction();

                // Throw Exception
                throw (new DataBaseException("Ocorreu um erro ao realizar uma exclusão", mappedEntityType, ex));
            }
        }

        #endregion

        #region Fields

        private ICrudFactory _Parent;

        #endregion

        #region Methods

        /// <summary>
        /// Cria uma Versão Pré-Compilada do Comando
        /// </summary>
        /// <param name="command"></param>
        private void CreateCompiledCommandVersion(IDbCommand command)
        {
            try
            {
                command.Prepare();
            }
            catch (InvalidOperationException)
            {
                // Handle Exception
            }
            catch (System.Exception e)
            {
#if DEBUG
                Debug.WriteLine("Error to Compile Command..: " + e.Message);
#endif
            }
        }

        /// <summary>
        /// Contains a Indication If Select Command Have a Compiled Version of Script
        /// </summary>
        private Hashtable _InsertScriptCompiledVersion = new Hashtable();

        /// <summary>
        /// Contains a Indication If Delete Command Have a Compiled Version of Script
        /// </summary>
        private Hashtable _DeleteScriptCompiledVersion = new Hashtable();

        /// <summary>
        /// Contains a Indication If Update Command Have a Compiled Version of Script
        /// </summary>
        private Hashtable _UpdateScriptCompiledVersion = new Hashtable();
        
        /// <summary>
        /// Get a New Value Of Entity PrimaryKey from @@Identity of DataBase
        /// </summary>
        /// <param name="mappedEntity">Saved Entity</param>
        private void GetNewPK(BusinessEntityBase mappedEntity, CRUDHelper crudHelper)
        {
            // Create Objects
            List<ExtendedPropertyInfo> primaryKeys;
            IDbCommand dbCommand;

            // Get PrimaryKeys
            primaryKeys = MappedEntityHelper.Instance.GetPrimaryKey(mappedEntity, this._Parent);

            // Check If Have a AutoGenerated
            foreach (ExtendedPropertyInfo primaryKey in primaryKeys)
            {
                if (primaryKey.EntityPrimaryKey.Identity)
                {
                    // Get Value
                    dbCommand =
                        crudHelper.GetDataBaseFactory(MappedEntityHelper.Instance.GetConnectionStringKey(mappedEntity)).
                            GetNewCommand();

                    // Set Command
                    dbCommand.CommandText = "SELECT @@Identity";
                    dbCommand.CommandType = CommandType.Text;

                    if (!this._IsFristGetNewPKScript)
                    {
                        // Pre-Compile Script
                        this.CreateCompiledCommandVersion(dbCommand);

                        // Update Flag
                        this._IsFristGetNewPKScript = true;
                    }

                    try
                    {
                        // Execute Command
                        primaryKey.PropertyType.SetValue(mappedEntity, Convert.ToInt32(dbCommand.ExecuteScalar()), null);
                    }
                    catch (System.Exception ex)
                    {
                        // Rollback Transaction
                        crudHelper.RollbackTransaction();

                        // Throw Exception
                        throw (new DataBaseException("Ocorreu um erro ao carregar a primary key da entidade", mappedEntity.GetType(), ex));
                    }

                    // Sai do Laço
                    break;
                }
            }
        }

        /// <summary>
        /// Get a Custom Insert Command (Using Command Buffer) for MappedEntity
        /// </summary>
        /// <param name="entity">Mapped Entity to Generate a Custom Insert Command</param>
        /// <returns>Generated Insert Command</returns>
        private string GetInsertCommandText(BusinessEntityBase entity, CRUDHelper crudHelper)
        {
            // Create Cache Objects
            string cacheKey = "Insert" + entity.GetType();

            // Check Cache To Get a Cached Script
            if (CacheManager.Instance.CheckIfContains(cacheKey))
            {
                return (string)CacheManager.Instance.GetFromCache(cacheKey);
            }

            // Create Objects
            StringBuilder queryBuilder = new StringBuilder();

            // Create Base Query
            queryBuilder.AppendFormat("INSERT INTO {0}({1}) VALUES({2})",
                                      MappedEntityHelper.Instance.GetTableName(entity),
                                      MappedEntityHelper.Instance.GetDBInsertFields(entity, this._Parent),
                                      MappedEntityHelper.Instance.GetInsertParametersName(entity,
                                                                                          crudHelper.GetDataBaseFactory(
                                                                                              MappedEntityHelper.
                                                                                                  Instance.
                                                                                                  GetConnectionStringKey
                                                                                                  (entity)), this._Parent));

            // Add Into Insert Script Cache
            CacheManager.Instance.AddToCache(cacheKey, queryBuilder.ToString());

            // Return Data
            return queryBuilder.ToString();
        }

        /// <summary>
        /// Get a Custom Update Command (Using Command Buffer) for MappedEntity
        /// </summary>
        /// <param name="entity">Mapped Entity to Generate a Custom Update Command</param>
        /// <returns>Generated Update Command</returns>
        private string GetUpdateCommandText(BusinessEntityBase entity, CRUDHelper crudHelper)
        {
            // Create Cache Objects
            string cacheKey = "Update" + entity.GetType();

            // Check Cache To Get a Cached Script
            if (CacheManager.Instance.CheckIfContains(cacheKey))
            {
                return (string)CacheManager.Instance.GetFromCache(cacheKey);
            }

            // Create Objects
            StringBuilder queryBuilder = new StringBuilder();

            // Create Base Query
            queryBuilder.AppendFormat("UPDATE {0} SET {1} WHERE ({2})",
                                      MappedEntityHelper.Instance.GetTableName(entity),
                                      MappedEntityHelper.Instance.GetUpdateQuery(entity,
                                                                                 crudHelper.GetDataBaseFactory(
                                                                                     MappedEntityHelper.Instance.
                                                                                         GetConnectionStringKey(entity)),
                                                                                 this._Parent),
                                      MappedEntityHelper.Instance.GetPKWhereQueryClausule(entity,
                                                                                          crudHelper.GetDataBaseFactory(
                                                                                              MappedEntityHelper.
                                                                                                  Instance.
                                                                                                  GetConnectionStringKey
                                                                                                  (entity)), this._Parent));

            // Add Into Insert Script Cache
            CacheManager.Instance.AddToCache(cacheKey, queryBuilder.ToString());

            // Return Data
            return queryBuilder.ToString();
        }

        /// <summary>
        /// Get a Custom Delete All Command (Using Command Buffer) for MappedEntity
        /// </summary>
        /// <param name="entityType">Mapped Entity Type to Generate a Custom Delete Command</param>
        /// <returns>Generated Delete All Command</returns>
        private string GetDeleteAllCommandText(Type entityType)
        {
            return "DELETE FROM " + MappedEntityHelper.Instance.GetTableName(entityType);
        }

        /// <summary>
        /// Get a Custom Delete Command (Using Command Buffer) for MappedEntity
        /// </summary>
        /// <param name="entity">Mapped Entity to Generate a Custom Delete Command</param>
        /// <returns>Generated Delete Command</returns>
        private string GetDeleteCommandText(BusinessEntityBase entity, CRUDHelper crudHelper)
        {
            // Create Cache Objects
            string cacheKey = "Delete" + entity.GetType();

            // Check Cache To Get a Cached Script
            if (CacheManager.Instance.CheckIfContains(cacheKey))
            {
                return (string)CacheManager.Instance.GetFromCache(cacheKey);
            }

            // Create Objects
            StringBuilder queryBuilder = new StringBuilder();

            // Create Base Query
            queryBuilder.AppendFormat("DELETE FROM {0} WHERE ({1})",
                                      MappedEntityHelper.Instance.GetTableName(entity),
                                      MappedEntityHelper.Instance.GetPKWhereQueryClausule(entity,
                                                                                          crudHelper.GetDataBaseFactory(
                                                                                              MappedEntityHelper.
                                                                                                  Instance.
                                                                                                  GetConnectionStringKey
                                                                                                  (entity)), this._Parent));

            // Add Into Insert Script Cache
            CacheManager.Instance.AddToCache(cacheKey, queryBuilder.ToString());

            // Return Data
            return queryBuilder.ToString();
        }

        #endregion
    }
}
